<?php
/*******************************************************************************
 * LightPress Rewrite Rule Generator (http://lightpress.org)
 *
 * Copyright 2005 Jerome Lavigne
 * Released under the GPL http://www.gnu.org/copyleft/gpl.html
 *
 * $Id: LPRewriteRules.php,v 1.3 2005/06/29 19:56:00 darkcanuck Exp $
 ******************************************************************************/

class LPRewriteRules {

    var $output_array = false;
    
    var $prefix = array('post' =>     array(),
                        'category' => array(),
                        'archives' => array(),
                        'static' =>   array(),
                        'search' =>   array(),
                        'author' =>   array(),
                        );
                        
    var $suffix = array('post' => array('regex' => '', 'args' => ''));
    
    var $_replace  = array('year'      => array('reg'=>'([0-9]{4})', 'arg'=>'y='),
                           'monthnum'  => array('reg'=>'([0-9]{2})', 'arg'=>'m='),
                           'day'       => array('reg'=>'([0-9]{2})', 'arg'=>'d='),
                           'hour'      => array('reg'=>'([0-9]{2})', 'arg'=>'hr='),
                           'minute'    => array('reg'=>'([0-9]{2})', 'arg'=>'min='),
                           'second'    => array('reg'=>'([0-9]{2})', 'arg'=>'sec='),
                           'postname'  => array('reg'=>'([^/]+)',    'arg'=>'p='),
                           'post_id'   => array('reg'=>'([0-9]+)',   'arg'=>'pi='),
                           'category'  => array('reg'=>'([^/]+)',    'arg'=>'c='),
                           'author'    => array('reg'=>'([^/]+)',    'arg'=>'u='),
                           // not part of WP rules, but use similar structure
                           'archives' => array('reg' => '([0-9]{6})','arg' => 'a='),
                           'static' =>   array('reg' => '([^/]+)',   'arg' => 's='),
                           'search' =>   array('reg' => '([^/]+)',   'arg' => 'q='),
                           );
        
    var $_lp_option = 'lp_opt_';
    
    var $_plugin_url = 'wp-content/plugins';
    var $_plugin_dir = '';
    
    var $_index = 'index.php';
    var $_feed = 'feed.php';
    var $_sitemap = 'sitemap.php';
    
    var $setting_names = array('basedir', 'url', 'wp_url');
    var $settings = array();
    
    var $rewrite_base = '/';
    var $rewrite_target = '/index.php';
    
    function LPRewriteRules($basedir = '') {
        
        // initialization
        $this->_plugin_dir = str_replace('/', DIRECTORY_SEPARATOR, $this->_plugin_url);
        
        // store lightpress 'home' directory
        if (!empty($basedir))
            $this->settings['basedir'] = $basedir;
        
        // prep settings array
        foreach($this->prefix as $k=>$v)
            $this->setting_names[] = $k . '_prefix';
        foreach($this->suffix as $k=>$v)
            $this->setting_names[] = $k . '_suffix';
        
        $this->setParameters();    
    }

    function setParameters($params = null) {
        if (!is_array($params))
            $params = array();
        
        // use setting if provided, otherwise load from db if not already set
        foreach($this->setting_names as $name) {
            if (isset($params[$name]))
                $this->settings[$name] = $params[$name];
            elseif (!isset($this->settings[$name]))
                $this->settings[$name] = get_settings($this->_lp_option . $name);
        }
        
        // figure out rewrite base URI
        $this->rewrite_base = $this->getUrlPath($this->settings['url']);
        
        // figure out rewrite target URI
        $basedir =& $this->settings['basedir'];
        $pos = strpos($basedir, $this->_plugin_dir);
        if ($pos === false) {
            // not running from plugin dir, just use index file
            $this->rewrite_target = '/';
        } else {
            // running as a plugin, figure out plugin dir name and construct url
            $this->rewrite_target = $this->_plugin_url;
            $path = explode(DIRECTORY_SEPARATOR, substr($basedir, $pos + strlen($this->_plugin_dir)));
            foreach($path as $dir) {
                if ($dir != '')
                    $this->rewrite_target .= '/' . $dir;
            }
            $this->rewrite_target .= '/';
        }
        
        // create regexps for context prefix/suffixes
        foreach($this->prefix as $name=>$val)
            $this->prefix[$name] = $this->getRegex($this->settings[$name . '_prefix'], $name);
        foreach($this->suffix as $name=>$val)
            $this->suffix[$name] = array('regex'=>$this->settings[$name . '_suffix'], 'args'=>array());
    }

    function getUrlPath($url) {
        $url_data = parse_url($url);
        if (substr($url_data['path'], -1) == '/')
            return($url_data['path']);
        else
            return($url_data['path'] . '/');
    }

    function getRegex($prefix, $context) {
        
        $replace =& $this->_replace;
        $regex = $prefix;
        
        // add context-specific names to prefix if not already there
        if ($context == 'post')
            $context = 'postname';  // default for post context
        $found = strpos($regex, '{' . $context . '}');
        if ($found === false) {
            switch($context) {
                case 'postname':            // also allow the post name (preferred)
                    $found = strpos($regex, '{post_id}');
                    break;
                case 'archives':        // can also allow year + month instead of {archives}
                    $found = ( (strpos($regex, '{year}') !== false) &&
                               (strpos($regex, '{monthnum}') !== false) );
                    break;
            }
        }
        if (($found === false) && isset($replace[$context])) {
            if (substr($regex, -1) != '/')
                $regex .= '/';
            $regex .= '{' . $context . '}';
        }
        
        // get arguments in order & replace with regex
        $args = array();
        if (preg_match_all('/{([^}]+)}/', $regex, $matches)) {
            foreach($matches[1] as $name) {
                if (isset($replace[$name])) {
                    $args[] = $replace[$name]['arg']; // add query var to argument list
                    $regex = str_replace('{' . $name . '}', $replace[$name]['reg'], $regex);
                } else {
                    // unknown permalink verb, remove parentheses
                    $regex = str_replace('{' . $verb . '}', $verb, $regex);
                }
            }
        }
        return (array('regex'=>$regex, 'args'=>$args));
    }
    
    function generate() {
        // setup
        $base   =& $this->rewrite_base;
        $target =& $this->rewrite_target;
        $index   = $target . $this->_index;
        $feed    = $target . $this->_feed;
        $sitemap = $target . $this->_sitemap;
        $rules = array();
        
        // basic rules (enable engine, set base, check for existing files, etc.)
        $rules[0] = '<IfModule mod_rewrite.c>';
        $rules[] = 'RewriteEngine On';
        $rules[] = 'RewriteBase ' . $base;
        $rules[] = 'RewriteCond %{REQUEST_FILENAME} -f [OR]';
        $rules[] = 'RewriteCond %{REQUEST_FILENAME} -d';
        $rules[] = 'RewriteRule ^.*$ - ' ;  // finish this rule with [S=###] once we have a count of the rest
        $count_basic = count($rules);
        
        // basic links
        $rules[] = "RewriteRule ^sitemap_index.xml $sitemap [L]";
        $rules[] = "RewriteRule ^trackback/([^/]+)/?$ $index?p=$1&action=trackback [QSA,L]";
        
        $modifiers = array(
                    array('url'=>'feed/(feed|rdf|rss|rss2|atom)(.xml)?/?', 'target'=>$feed, 'arg'=>'feed='),
                    array('url'=>'(feed|rdf|rss|rss2|atom)(.xml)?/?', 'target'=>$feed, 'arg'=>'feed='),
                    array('url'=>'sitemap.xml', 'target'=>$sitemap, 'arg'=>'sitemap=1'),
                    array('url'=>'record/?([0-9]*)/?', 'target'=>$index, 'arg'=>'i='),
                    array('url'=>'?', 'target'=>$index, 'arg'=>'')
                    );
        
        // context links
        foreach ($this->prefix as $context=>$data) {
            foreach($modifiers as $m) {
                switch($context) {      // always fall through unless we want to exclude the current rule
                    case 'post':
                    case 'static':
                    case 'search':
                    case 'author':
                        if ($m['target'] == $sitemap)
                            break;
                    default:
                        $rules[] = $this->createRule($context, $m['url'], $m['target'], $m['arg']);
                }
            }
        }

        // still unclear why this rule is needed over the one above
        /* RewriteRule ^archives/([0-9]{4})/([0-9]{2})/sitemap.xml$ feed.php?y=$1&m=$2&feed=sitemap&sitemap=1 [QSA,L] */
        
        // index links
        foreach($modifiers as $m) {
            if (!empty($m['url']) && ($m['url'] != '?'))
                $rules[] = $this->createRule('index', $m['url'], $m['target'], $m['arg']);
        }
        
        // feeds (WP compatibility)
        $rules[] = $this->createRule('', '(wp-rss.php|wp-rss2.php|wp-rdf.php)', $feed, '', '[L]');
        $rules[] = $this->createRule('', 'wp-atom.php', $feed, '', '[L]');
        
        // redirect *everything* else if we're running out of a subdir
        if ($target != '/')
            $rules[] = 'RewriteRule ^(.*) ' . $target . '$1 [QSA,L]';
        
        // add count of rules to skip after dir/file match rule
        $count_rules = count($rules) - $count_basic;
        $rules[$count_basic - 1] .= "[S=$count_rules]";
        
        // redirect WP feed files and all files at base url if we're running out of a subdir
        if ($target != '/') {
            // feeds (WP compatibility)
            $rules[] = 'RewriteRule ^(wp-rss.php|wp-rss2.php|wp-rdf.php) ' . $feed . ' [L]';
            $rules[] = 'RewriteRule ^wp-atom.php ' . $feed . ' [L]';
            $rules[] = 'RewriteRule ^([^/]*)$ ' . $target . '$1 [QSA,L]';
        }
        
        //die ('<pre>' . implode("\n", $rules) . '</pre>');
        
        if ($this->output_array)
            return $rules;
        else
            return implode("\n", $rules);
    }


    function createRule($context, $url, $target, $args, $type='[QSA,L]') {
        
        $rule = 'RewriteRule ^';
        $arglist = array();
        
        if (isset($this->prefix[$context])) {   // context-specific prefix
            $rule .= $this->prefix[$context]['regex'];
            $arglist = array_merge($arglist, $this->prefix[$context]['args']);
        }
        
        if (isset($this->suffix[$context])) {    // context-specific suffix
            $rule .= $this->suffix[$context]['regex'];
            $arglist = array_merge($arglist, $this->suffix[$context]['args']);
        }

        $rule .= '/' . $url;                          // remainder of url
        
        $rule .= '$ ' . $target;                // target file
        $arglist = array_merge($arglist, $args);
        
        // append arguments and set pattern match values
        $first = true;
        $count = 1;
        foreach($arglist as $arg) {
            if (!empty($arg) || ($arg == '0')) {
                $rule .= ($first ? '?' : '&') . $arg;
                if (substr($arg, -1) == '=') {
                    $rule .= '$' . $count;
                    $count++;
                }
                $first = false;
            }
        }
        
        return($rule . ' ' . $type);
    }
}

?>